// Copyright (c) 2025 ReactiveUI. All rights reserved.
// Licensed to ReactiveUI under one or more agreements.
// ReactiveUI licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

namespace Splat.Builder;

/// <summary>
/// A builder class for configuring ReactiveUI without using reflection.
/// This provides an AOT-compatible alternative to the reflection-based InitializeReactiveUI method.
/// </summary>
public class AppBuilder : IAppBuilder, IAppInstance
{
    private readonly List<Action<IMutableDependencyResolver>> _registrations = [];
    private Func<IMutableDependencyResolver> _resolverProvider;
    private Func<IReadonlyDependencyResolver?> _serviceProvider;

    /// <summary>
    /// Initializes a new instance of the <see cref="AppBuilder" /> class.
    /// </summary>
    /// <param name="resolver">The dependency resolver to configure.</param>
    /// <param name="current">The configured services.</param>
    /// <exception cref="ArgumentNullException">resolver.</exception>
    public AppBuilder(IMutableDependencyResolver resolver, IReadonlyDependencyResolver? current = null)
    {
        UsingBuilder = true;
        CurrentMutable = resolver ?? throw new ArgumentNullException(nameof(resolver));
        Current = current;
        _resolverProvider = () => CurrentMutable;
        _serviceProvider = () => Current;
    }

    /// <summary>
    /// Gets a value indicating whether this instance has been built.
    /// </summary>
    /// <value>
    ///   <c>true</c> if this instance has been built; otherwise, <c>false</c>.
    /// </value>
    public static bool HasBeenBuilt { get; private set; }

    /// <summary>
    /// Gets a value indicating whether the application builder is being used.
    /// </summary>
    /// <value>
    ///   <c>true</c> if using the application builder; otherwise, <c>false</c>.
    /// </value>
    public static bool UsingBuilder { get; private set; }

    /// <summary>
    /// Gets the current services.
    /// </summary>
    /// <value>
    /// The services.
    /// </value>
    public IReadonlyDependencyResolver? Current { get; private set; }

    /// <summary>
    /// Gets the mutable service registrar.
    /// </summary>
    /// <value>
    /// The current mutable.
    /// </value>
    public IMutableDependencyResolver CurrentMutable { get; }

    /// <summary>
    /// Creates a Splat builder with the Splat Locator instance using the current mutable resolver.
    /// </summary>
    /// <returns>The builder instance for chaining.</returns>
    public static AppBuilder CreateSplatBuilder() => new(AppLocator.CurrentMutable, AppLocator.Current);

    /// <summary>
    /// Resets the builder state for tests, ONLY if the builder is being used in a unit test environment.
    /// </summary>
    public static void ResetBuilderStateForTests()
    {
        HasBeenBuilt = false;
        UsingBuilder = false;
    }

    /// <summary>
    /// Direct the builder to use the current Splat Locator (Locator.CurrentMutable)
    /// for subsequent registrations. This is useful when configuring an external
    /// container (e.g., Autofac, DryIoc, Microsoft.Extensions.DependencyInjection)
    /// as the Splat dependency resolver prior to applying ReactiveUI registrations.
    /// </summary>
    /// <returns>The builder instance for chaining.</returns>
    public IAppBuilder UseCurrentSplatLocator()
    {
        _resolverProvider = () => AppLocator.CurrentMutable;
        _serviceProvider = () => AppLocator.Current;
        return this;
    }

    /// <summary>
    /// Using the splat module.
    /// </summary>
    /// <typeparam name="T">The Splat Module Type.</typeparam>
    /// <param name="registrationModule">The registration module to add.</param>
    /// <returns>
    /// The builder instance for method chaining.
    /// </returns>
    public IAppBuilder UsingModule<T>(T registrationModule)
        where T : IModule
    {
        registrationModule.ThrowArgumentNullExceptionIfNull(nameof(registrationModule));
        _registrations.Add(registrationModule.Configure);
        return this;
    }

    /// <summary>
    /// Registers a custom registration action.
    /// </summary>
    /// <param name="configureAction">The configuration action to add.</param>
    /// <returns>The builder instance for method chaining.</returns>
    public IAppBuilder WithCustomRegistration(Action<IMutableDependencyResolver> configureAction)
    {
        configureAction.ThrowArgumentNullExceptionIfNull(nameof(configureAction));

        _registrations.Add(configureAction);
        return this;
    }

    /// <summary>
    /// Registers the core ReactiveUI services.
    /// </summary>
    /// <returns>The builder instance for method chaining.</returns>
    public virtual IAppBuilder WithCoreServices() => this;

    /// <summary>
    /// Builds and applies all registrations to the dependency resolver.
    /// </summary>
    /// <returns>
    /// An App Instance.
    /// </returns>
    public IAppInstance Build()
    {
        // If the builder has already been built, do nothing.
        if (HasBeenBuilt)
        {
            return this;
        }

        // Mark as initialized using the builder so reflection-based initialization is disabled.
        HasBeenBuilt = true;

        // Ensure core services are always registered
        WithCoreServices();

        // Apply all registrations against the current resolver source
        foreach (var registration in _registrations)
        {
            var targetResolver = _resolverProvider();
            registration(targetResolver);
        }

        Current = _serviceProvider();
        return this;
    }
}
